#include <process.h>
#include <sys/sysdef.h>
#include <mm/mmap.h>
#include <mm/vma.h>
#include <lib/math.h>
#include <log.h>

bool is_address_overlap(uptr vaddr, struct mmap_args* args) {
    struct vma* vma = args->mm->vma_list;
    uptr end = vaddr + args->len;
    for (; vma; vma = vma->next) {
        if (vaddr >= vma->start && vaddr < vma->end) {
            return true;
        }

        if (end > vma->start && end <= vma->end) {
            return true;
        }
    }
    return false;
}

uptr find_free_slot(struct mmap_args* args) {
    uptr res = 0;
    struct vma* vma = args->mm->vma_list;
    uptr size = args->len;
    if (args->mm->vma_count == 0) {
        res = USER_MM_BASE;
    }
    for (; vma; vma = vma->next) {
        if (vma->next) {
            if (vma->next->start - vma->end > size) {
                res = vma->end;
                break;
            }
        } else if (!vma->prev && vma->start - USER_MM_BASE > size) {
            res = vma->start - size;
            break;
        } else if (vma->end + size < KERNEL_MM_BASE) {
            res = vma->end;
            break;
        }
    }
    return res;
}

u32 convert_to_vma_flag(struct mmap_args* args) {
    u32 flags = 0;
    u32 prot = args->prot ? args->prot : PROT_READ | PROT_WRITE;

    if (args->prot & PROT_WRITE) {
        flags |= VMA_WRITE;
        if (args->flags & MMAP_SHARED) {
            flags |= VMA_WRITE_SHARE;
        }
    } else {
        flags |= VMA_READ;
        if (args->flags & MMAP_SHARED) {
            flags |= VMA_READ_SHARE;
        }
    }

    if (args->flags & MMAP_PRIVATE) {
        flags |= VMA_PRIVATE;
    }

    return flags;
}

int do_memmap(uptr* addr_out, uptr vaddr, file_t* file, struct mmap_args* args) {

    mutex_lock(&args->mm->lock);

    if (args->flags & MMAP_FIXED) {
        if (is_address_overlap(vaddr, args)) {
            return EINVAL;
        }
    } else {
        if (args->flags & MMAP_REPLACE) {
            // TODO: replace
            return ENOSET;
        }
        if (!(vaddr = find_free_slot(args))) {
            return ENOMEM;
        }
    }

    struct vma* vma = vma_alloc();
    if (!vma) {
        return ENOMEM;
    }
    vma->start = vaddr;
    vma->end = ICEIL(vaddr + args->len, PG_SIZE) << PG_SIZE_BITS;
    vma->flags = convert_to_vma_flag(args);
    vma_list_append(args->mm, vma);

    if (file) {
        vma->file = file;
        vma->file_offset = args->file_offset;
        atomic_fetch_add(&file->ref_count, 1);
    }

    if (addr_out) {
        *addr_out = vaddr;
    }
    mutex_unlock(&args->mm->lock);
    return 0;
}

__DEF_SYSCALL6(void*, mmap, void*, addr, size_t, len, int, prot, int, flags, int, fd, u32, offset) {
    int fd = va_arg(__args, int);
    u32 offset = va_arg(__args, u32);
    fd_t* fd_ = NULL;
    file_t* file = NULL;
    int errno;
    if (!(flags & MMAP_ANONYMOUS)) {
        if ((errno = fs_getfd(fd, &fd_))) {
            goto out;
        }
        file = fd_->file;
    }

    struct mmap_args args = {
        .file_offset = offset,
        .len = len,
        .prot = prot,
        .flags = flags,
        .mm = cur_proc->mm
    };

    uptr addr_out = 0;
    errno = do_memmap(&addr_out, (uptr)addr, file, &args);

out:
    cur_proc->k_status = errno;
    return errno ? NULL : (void*)addr_out;
}